using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;
using Atomic.Structures;

namespace Atomic.Interfaces
{
	/// <summary>
	/// Plotting of structures using Asymptote (Debian package asymptote).
	/// </summary>
	public static class Asymptote
	{
		public static void Plot(Structure structure, FileInfo file)
		{
			// Inspiration from this: http://tex.stackexchange.com/a/97394
			
			Dictionary<Atom, string> surfaces = new Dictionary<Atom, string>();
			surfaces.Add(Atom.Fe, "scale3(1.26)*unitsphere");
			surfaces.Add(Atom.N, "scale3(0.74)*unitsphere");
			surfaces.Add(Atom.Vacancy, "scale3(0.74)*unitsphere");

			// http://asymptote.sourceforge.net/doc/Pens.html
			Dictionary<Atom, string> materials = new Dictionary<Atom, string>();
			//materials.Add(Atom.Fe, "material(diffusepen=0.15*rgb(181,113,0),emissivepen=rgb(181,113,0))");
			//materials.Add(Atom.N, "material(diffusepen=0.15*rgb(176,185,230),emissivepen=rgb(176,185,230))");
			materials.Add(Atom.Fe, "material(diffusepen=orange,emissivepen=orange)");
			materials.Add(Atom.N, "material(diffusepen=blue,emissivepen=lightblue)");
			materials.Add(Atom.Vacancy, "material(diffusepen=gray(0.4),emissivepen=gray(0.6))");

			using (TempDirectory temp = new TempDirectory())
			{
				double scaling = 50.0;
				
				Lattice lattice = structure.Lattice;

				using (StreamWriter plot = new StreamWriter(temp.Directory.AddFile("plot.asy").FullName))
				{
					plot.WriteLine("import three;");
					//plot.WriteLine("currentprojection=perspective(300,-650,500,center=true);");
					//plot.WriteLine("currentprojection=orthographic(4,6,3);");
					SpaceVector camera = lattice.A.PhysicalCoordinates - 0.8 * lattice.B.PhysicalCoordinates + 0.3 * lattice.C.PhysicalCoordinates;
					plot.WriteLine(string.Format(CultureInfo.InvariantCulture, "currentprojection=orthographic({0:r},{1:r},{2:r});", camera.X, camera.Y, camera.Z));

					// Define atoms.
					plot.WriteLine("surface iona = scale3(20)*unitsphere;");
					plot.WriteLine("surface ionb = scale3(25)*unitsphere;");

					// surface properties and color of the ions
					plot.WriteLine("material White = material(diffusepen=gray(0.4),emissivepen=gray(0.6));");
					plot.WriteLine("material Red = material(diffusepen=red,emissivepen=lightred);");

					// Style of lines connecting ions.
					plot.WriteLine("pen thick=linewidth(2);");

					List<Site> sites = new List<Site>();
					foreach (Site site in structure.Normalize().Sites)
					{
						for (int i = 0; i <= 1; i++)
						{
							for (int j = 0; j <= 1; j++)
							{
								for (int k = 0; k <= 1; k++)
								{
									SpaceVector p0 = lattice.DecomposeRounded(new Position(site.Position.PhysicalCoordinates + i * lattice.A.PhysicalCoordinates + j * lattice.B.PhysicalCoordinates + k * lattice.C.PhysicalCoordinates)).Coordinates;

									double x = p0[0];
									double y = p0[1];
									double z = p0[2];

									if (x >= 0.0 && x <= 1.0 && y >= 0.0 && y <= 1.0 && z >= 0.0 && z <= 1.0)
									{
										Position position = new Position(lattice, new SpaceVector(x, y, z));
										sites.Add(new Site(site.Atom, position));
									}
								}
							}
						}
					}

					foreach (Site site in sites)
					{
						//plots.Add(new SpaceArrow(site.Position.PhysicalCoordinates, site.Position.PhysicalCoordinates + delta));
						//plot.WriteLine(string.Format(CultureInfo.InvariantCulture, "draw(shift({0:r}*({1:r},{2:r},{3:r}))*ionb,Red);", scaling, site.Position.PhysicalCoordinates.X, site.Position.PhysicalCoordinates.Y, site.Position.PhysicalCoordinates.Z));
						plot.WriteLine(string.Format(CultureInfo.InvariantCulture, "draw(shift({0:r}*({1:r},{2:r},{3:r}))*scale3(0.4*{0:r})*{4},{5});", scaling, site.Position.PhysicalCoordinates.X, site.Position.PhysicalCoordinates.Y, site.Position.PhysicalCoordinates.Z, surfaces[site.Atom], materials[site.Atom]));
					}
				
					foreach (SpaceVector p1 in new SpaceVector[] { lattice.A.PhysicalCoordinates, lattice.B.PhysicalCoordinates, lattice.C.PhysicalCoordinates })
					{
						plot.WriteLine(string.Format(CultureInfo.InvariantCulture, "draw({0:r}*({1:r},{2:r},{3:r})--{0:r}*({4:r},{5:r},{6:r}),thick);", scaling, 0.0, 0.0, 0.0, p1.X, p1.Y, p1.Z));
						//plots.Add(new SpaceLine(SpaceVector.Zero, p1));
						foreach (SpaceVector p2 in new SpaceVector[] { lattice.A.PhysicalCoordinates, lattice.B.PhysicalCoordinates, lattice.C.PhysicalCoordinates })
						{
							if (p1 != p2)
							{
								//plots.Add(new SpaceLine(p1, p1 + p2));
								plot.WriteLine(string.Format(CultureInfo.InvariantCulture, "draw({0:r}*({1:r},{2:r},{3:r})--{0:r}*({4:r},{5:r},{6:r}),thick);", scaling, p1.X, p1.Y, p1.Z, p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z));
								foreach (SpaceVector p3 in new SpaceVector[] { lattice.A.PhysicalCoordinates, lattice.B.PhysicalCoordinates, lattice.C.PhysicalCoordinates })
								{
									if (p1 != p3 && p2 != p3)
									{
										//plots.Add(new SpaceLine(p1 + p2, p1 + p2 + p3));
										plot.WriteLine(string.Format(CultureInfo.InvariantCulture, "draw({0:r}*({1:r},{2:r},{3:r})--{0:r}*({4:r},{5:r},{6:r}),thick);", scaling, p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z, p1.X + p2.X + p3.X, p1.Y + p2.Y + p3.Y, p1.Z + p2.Z + p3.Z));
									}
								}
							}
						}
					}

					for (int x=-1; x<2; ++x)
					{
  						for (int y=-1; y<2; ++y)
						{
    						for (int z=-1; z<2; ++z)
							{
								//plot.WriteLine("draw(shift(100*({0},{1},{2}))*iona,White);", x, y, z);
    						}
  						}
					}

					for(int x=-1; x<2; ++x)
					{
  						for(int y=-1; y<2; ++y)
						{
    						for(int z=-1; z<2; ++z)
							{
      							if (x<1)
								{
									//plot.WriteLine("draw(100*({0},{1},{2})--100*({0}+1,{1},{2}),thick);", x, y, z);
								}
      							if (y<1)
								{
									//plot.WriteLine("draw(100*({0},{1},{2})--100*({0},{1}+1,{2}),thick);", x, y, z);
								}
      							if (z<1)
								{
									//plot.WriteLine("draw(100*({0},{1},{2})--100*({0},{1},{2}+1),thick);", x, y, z);
								}
    						}
  						}
					}

					for(int x=-1; x<2; x+=2)
					{
  						for(int y=-1; y<2; y+=2)
						{
    						for(int z=-1; z<2; z+=2)
							{
								//plot.WriteLine("draw(shift(50*({0},{1},{2}))*ionb,Red);", x, y, z);
    						}
  						}
					}
				}

				Process p = new Process();
				p.StartInfo.FileName = "asy";
				p.StartInfo.Arguments = "plot.asy";
				p.StartInfo.WorkingDirectory = temp.Directory.FullName;
				p.StartInfo.UseShellExecute = false;
				p.StartInfo.RedirectStandardOutput = true;
				p.StartInfo.RedirectStandardError = true;
				p.Start();
				p.WaitForExit();

				if (p.ExitCode != 0)
				{
					throw new Exception(string.Format("Running asy failed: {0}", p.StandardError.ReadToEnd()));
				}

				temp.Directory.AddFile("plot.eps").CopyTo(file.FullName, true);

				p = new Process();
				p.StartInfo.FileName = "gnome-open";
				p.StartInfo.Arguments = file.FullName;
				p.StartInfo.WorkingDirectory = temp.Directory.FullName;
				//p.StartInfo.UseShellExecute = false;
				//p.StartInfo.RedirectStandardError = true;
				p.Start();
				p.WaitForExit();
			}
		}
	}
}
